package fag.editor;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.event.EventListenerList;

import fag.core.model.Animation;
import fag.core.model.Sprite;

/**
 * Vue chronologique d'une animation.
 * @author lumz
 */
public class Timeline extends JPanel implements MouseListener {

	private static final long serialVersionUID = 474319381068539852L;
	
	private final static int DEFAULT_WIDTH = 600;
	private final static int DEFAULT_HEIGHT = 60;
	
	private final static int SPANNING = 3;
	
	private final static Color BACKGROUND_COLOR = Color.WHITE;
	private final static Color SELECTION_COLOR = Color.GRAY;
	private final static Font FONT = new Font("Arial", Font.BOLD, 10);
	
	protected final static String ACTION_SELECTION_FRAME_CHANGED = "Selection Frame changed";
	
	private final EventListenerList listeners = new EventListenerList();
	
	private int totalWidth = DEFAULT_WIDTH;
	
	private Animation animation = null;
	private int selectedFrameIndex = 0;
	
	private int frameCellWidth = DEFAULT_HEIGHT;
	
	/**
	 * Constructeur par défaut.
	 */
	public Timeline() {
		
		setBackground(BACKGROUND_COLOR);
		updateWidth();
		addMouseListener(this);
	}
	
	/**
	 * Ajouter un écouteur à cette vue chronologique.
	 * @param listener L'écouteur à ajouter.
	 */
	public void addActionListener(ActionListener listener) {
		listeners.add(ActionListener.class, listener);
	}
	
	/**
	 * Accesseur de l'index de la frame actuellement sélectionnée.
	 * @return L'index de la frame actuellement sélectionnée.
	 */
	public int getCurrentFrameIndex() {
		return selectedFrameIndex;
	}
	
	@Override
	public void mouseClicked(MouseEvent event) {		
	}

	@Override
	public void mouseEntered(MouseEvent event) {
	}

	@Override
	public void mouseExited(MouseEvent event) {
	}

	@Override
	public void mousePressed(MouseEvent event) {
		
		int old = selectedFrameIndex;
		selectedFrameIndex = (int)(event.getX() / frameCellWidth);
		
		if(selectedFrameIndex < animation.getListFrame().size()) {
		
			repaint(frameCellWidth * old, 0, frameCellWidth, DEFAULT_HEIGHT);
			repaint(frameCellWidth * selectedFrameIndex, 0, frameCellWidth, DEFAULT_HEIGHT);
		
			if(old != selectedFrameIndex)
			fireSelectionChanged();
		}
	}

	@Override
	public void mouseReleased(MouseEvent event) {
	}
	
	@Override
	public void paintComponent(Graphics graphics) {
		
		Graphics2D graphics2d = (Graphics2D) graphics;
		
		graphics2d.clearRect(0, 0, totalWidth, DEFAULT_HEIGHT);
		
		if(animation != null && animation.getListFrame().size() >= 1) {
			
			// Modifier la police
			Font oldFont = graphics2d.getFont();
			
			graphics2d.setFont(FONT);
			FontMetrics fontMetrics = graphics2d.getFontMetrics();
			
			for(int i = 0; i < animation.getListFrame().size(); ++i) {
				
				// Changer la couleur de fond pour la frame sélectionnée
				if(i == selectedFrameIndex) {
					Color old = graphics2d.getColor();
					graphics2d.setColor(SELECTION_COLOR);
					graphics2d.fillRect(frameCellWidth * selectedFrameIndex, 0, frameCellWidth, DEFAULT_HEIGHT);
					graphics2d.setColor(old);
				}
				
				// Afficher le délimiteur
				graphics2d.drawLine((i + 1) * frameCellWidth, 0, (i + 1) * frameCellWidth, DEFAULT_HEIGHT);
				
				// Afficher une miniature de l'image
				Sprite sprite = animation.getListFrame().get(i).getSprite();
				BufferedImage bufferedImage = sprite.getBufferedImage();
				
				if(bufferedImage != null) {
				
				double scale = Math.min((double)(frameCellWidth - SPANNING * 2) / bufferedImage.getWidth(), (double)(DEFAULT_HEIGHT - SPANNING * 2) / bufferedImage.getHeight());
				
				graphics2d.scale(scale, scale);
				graphics2d.drawImage(bufferedImage, null, (int)((frameCellWidth - bufferedImage.getWidth() * scale) / 2 / scale) + (int)(frameCellWidth / scale * i), (int)((DEFAULT_HEIGHT - bufferedImage.getHeight() * scale) / 2 / scale));
				graphics2d.scale(1 / scale, 1 / scale);
				}
			
				// Afficher la durée de la frame
				graphics2d.drawString(String.valueOf(animation.getListFrame().get(i).getDuration()) + " ms", (i * frameCellWidth) + SPANNING, fontMetrics.getAscent());
			}
			
			// Restaurer le contexte de dessin
			graphics2d.setFont(oldFont);
		}
	}
	
	@Override
	public Dimension preferredSize() {
		return new Dimension(totalWidth, DEFAULT_HEIGHT);
	}
	
	/**
	 * Retirer un écouteur de la vue chronologique.
	 * @param listener L'écouteur à retirer.
	 */
	public void removeActionListener(ActionListener listener) {
		listeners.remove(ActionListener.class, listener);
	}
	
	/**
	 * Fixer la largeur d'une cellule pour frame.
	 * @param frameCellWidth La largeur d'une cellule pour frame.
	 */
	public void setFrameCellWidth(int frameCellWidth) {
		this.frameCellWidth = frameCellWidth;
		updateWidth();
	}
	
	/**
	 * Fixer l'animation courante de la vue.
	 * @param model L'animation à visionner.
	 */
	public void setAnimation(Animation animation) {
		this.animation = animation;
		selectedFrameIndex = 0;
		updateWidth();
	}
	
	/**
	 * Avertir les listeners d'un changement de sélection.
	 */
	protected void fireSelectionChanged() {
		for(ActionListener listener : listeners.getListeners(ActionListener.class))
			listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_LAST + 1, ACTION_SELECTION_FRAME_CHANGED));
	}
	
	/**
	 * Mettre à jour la largeur du composant.
	 */
	protected void updateWidth() {
		
		// Trouver la largeur actuelle
		if(animation != null)
			totalWidth = frameCellWidth * animation.getListFrame().size();
		else
			totalWidth = DEFAULT_WIDTH;
		
		// Fixer la taille du composant
		setSize(totalWidth, DEFAULT_HEIGHT);
		
		// Repeindre
		repaint();
	}
}
